﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Bamboo.Utils
{
    public class TextFileCheckException : Exception
    {
        public TextFileCheckException(string s) : base(s) { }
    }

    public class TextFileChecker
    {
        /// <summary>
        /// 分隔符
        /// </summary>
        private List<string> _locationKeys = new List<string>();
        /// <summary>
        /// 正确关键词
        /// </summary>
        private HashSet<string> _correctKeys = new HashSet<string>();
        /// <summary>
        /// 错误关键词
        /// </summary>
        private HashSet<string> _errorKeys = new HashSet<string>();
        /// <summary>
        /// 忽略关键词
        /// </summary>
        private HashSet<string> _ignoreKeys = new HashSet<string>();
        /// <summary>
        /// 待检查的文件名
        /// </summary>
        private HashSet<string> _files = new HashSet<string>();

        /// <summary>
        /// 待检查的内容
        /// </summary>
        private Dictionary<string, string> _contents = new Dictionary<string, string>();
        /// <summary>
        /// 错误信息
        /// </summary>
        private StringBuilder _errorText = new StringBuilder();

        private Encoding _encoding = Encoding.GetEncoding("gbk");

        /// <summary>
        /// 初始化检查器
        /// </summary>
        public void Init()
        {
            _locationKeys.Clear();
            _correctKeys.Clear();
            _ignoreKeys.Clear();
            _errorKeys.Clear();
            _errorText.Clear();
            _files.Clear();
            _contents.Clear();
        }

        /// <summary>
        /// 设置字符集
        /// </summary>
        /// <param name="encoding"></param>
        public void SetEncoding(string encoding)
        {
            _encoding = Encoding.GetEncoding(encoding);
        }

        /// <summary>
        /// 添加分隔关键词.
        /// 分隔关键词将文本文件分隔成若干块, 当显示错误的时候会整块显示.
        /// </summary>
        /// <param name="key"></param>
        public void AddLocationKey(string key)
        {
            if (_locationKeys.Contains(key)) return;
            _locationKeys.Add(key);
        }

        /// <summary>
        /// 添加正确的关键词.
        /// 只有找到所有的正确关键词, 才认为一个文本文件是正确的.
        /// </summary>
        /// <param name="key"></param>
        public void AddCorrectKey(string key)
        {
            if (_correctKeys.Contains(key)) return;
            _correctKeys.Add(key);
        }

        /// <summary>
        /// 添加错误的关键词.
        /// 找到任何一个错误关键词, 即可认为一个文本文件是错误的.
        /// </summary>
        /// <param name="key"></param>
        public void AddErrorKey(string key)
        {
            if (_errorKeys.Contains(key)) return;
            _errorKeys.Add(key);
        }

        /// <summary>
        /// 添加忽略的关键词
        /// 主要为了防止错误误判
        /// </summary>
        /// <param name="key"></param>
        public void AddIgnoreKey(string key)
        {
            if (_ignoreKeys.Contains(key)) return;
            _ignoreKeys.Add(key);
        }

        /// <summary>
        /// 添加待检查错误的文件
        /// </summary>
        /// <param name="file"></param>
        public void AddFile(string file)
        {
            if (_files.Contains(file)) return;
            _files.Add(file);
        }

        public void AddContent(string name, string content)
        {
            _contents[name] = content;

        }

        public void ClearFiles()
        {
            _files.Clear();
        }

        public void ClearContents()
        {
            _contents.Clear();
        }
        /// <summary>
        /// 开始检查错误
        /// </summary>
        public void Execute()
        {
            _errorText.Clear();
            foreach (string file in _files)
            {
                CheckFile(file);
            }
            foreach (var pair in _contents)
            {
                CheckContent(pair.Key, pair.Value.ToString());
            }
            if (_errorText.Length > 0)
            {
                throw new TextFileCheckException("文本检查不通过, 以下是错误摘要\r\n====================\r\n" +
                    _errorText + "====================\r\n");
            }
        }

        private void CheckFile(string filename)
        {
            FileInfo file = new FileInfo(filename);
            if (file.Exists == false)
            {
                _errorText.AppendLine(">>>>找不到文件. (" + filename + ")");
                return;
            }
            string content = File.ReadAllText(file.FullName, _encoding);
            CheckContent(filename, content);
        }

        private string GetMatchKey(string input, IEnumerable<string> keys)
        {
            foreach (var key in keys)
            {
                if (input.Contains(key)) return key;
            }
            return "";
        }
        private void CheckContent(string filename, string content)
        {

            string[] lines = content.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
            //检索文本
            List<TextKeyLine> keyLines = new List<TextKeyLine>(); //只保存错误和分隔行
            TextKeyLine lastSeperateLine = null;
            HashSet<string> _remainCorrectKeys = new HashSet<string>(_correctKeys);
            for (int i = 0; i < lines.Length; i++)
            {
                string line = lines[i].Trim();
                //找忽略行
                string ignoreKey = GetMatchKey(line, _ignoreKeys);
                if (!string.IsNullOrEmpty(ignoreKey))
                {
                    continue; //被忽略的行不处理
                }
                //找错误行
                string errorKey = GetMatchKey(line, _errorKeys);
                if (!string.IsNullOrEmpty(errorKey))
                {
                    //加入分隔行
                    if (lastSeperateLine != null)
                    {
                        keyLines.Add(lastSeperateLine);
                        lastSeperateLine = null;
                    }
                    keyLines.Add(new TextKeyLine(i, TextKeyLineMode.错误行));
                }
                //找正确行
                string correctKey = GetMatchKey(line, _correctKeys);
                if (!string.IsNullOrEmpty(correctKey))
                {
                    keyLines.Add(new TextKeyLine(i, TextKeyLineMode.正确行));
                    _remainCorrectKeys.Remove(correctKey);
                }
                //找定位行
                string locationKey = GetMatchKey(line, _locationKeys);
                if (!string.IsNullOrEmpty(locationKey))
                {
                    lastSeperateLine = new TextKeyLine(i, TextKeyLineMode.分隔行);
                }
            }
            //处理结果
            int errorCount = keyLines.Count(a => a.Mode == TextKeyLineMode.错误行);
            if (errorCount > 0)
            {
                _errorText.AppendLine(">>>>文件中发现错误(" + filename + "): ");
                foreach (var line in keyLines)
                {
                    _errorText.AppendLine($"{lines[line.LineNo]}\t\t\t(line={line.LineNo})");
                }
            }
            else if (_remainCorrectKeys.Count > 0)
            {
                _errorText.AppendLine(">>>>虽然文件中未发现错误, 但也未找到成功标志(" + filename + "): ");
                foreach (var key in _correctKeys)
                {
                    _errorText.AppendLine("  " + key);
                }
            }
            else
            {
                //文件正确
                return;
            }
        }
    }

    public class TextErrorBlock
    {
        public TextKeyLine BeginLine = null;
        public TextKeyLine EndLine = null;
        public List<TextKeyLine> ErrorLines = new List<TextKeyLine>();
    }

    public enum TextKeyLineMode { 分隔行 = 1, 正确行 = 2, 错误行 = 3 }
    public class TextKeyLine
    {
        public int LineNo;
        public TextKeyLineMode Mode;
        public TextKeyLine(int no, TextKeyLineMode mode)
        {
            LineNo = no;
            Mode = mode;
        }
    }
}
